/*
 *
 *    Copyright (c) 2020-2021 Project CHIP Authors
 *    Copyright (c) 2016-2017 Nest Labs, Inc.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

/**
 *    @file
 *      This file contains declarations of the
 *      chip::System::Layer class and its related types, data and
 *      functions.
 */

#pragma once

#include <type_traits>
#include <utility>

// Include configuration headers
#include <system/SystemConfig.h>

#include <lib/core/CHIPCallback.h>
#include <lib/core/CriticalFailure.h>

#include <lib/support/CodeUtils.h>
#include <lib/support/DLLUtil.h>
#include <lib/support/LambdaBridge.h>
#include <system/SystemClock.h>
#include <system/SystemError.h>
#include <system/SystemEvent.h>

#if CHIP_SYSTEM_CONFIG_USE_SOCKETS
#include <lib/support/IntrusiveList.h>
#include <system/SocketEvents.h>
#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS

#if CHIP_SYSTEM_CONFIG_USE_DISPATCH
#include <dispatch/dispatch.h>
#elif CHIP_SYSTEM_CONFIG_USE_LIBEV
#include <ev.h>
#endif // CHIP_SYSTEM_CONFIG_USE_DISPATCH/LIBEV

namespace chip {
namespace System {

class Layer;
using TimerCompleteCallback = void (*)(Layer * aLayer, void * appState);

/**
 * This provides access to timers according to the configured event handling model.
 *
 * The abstract class hierarchy is:
 * - Layer: Core timer methods.
 *   - LayerFreeRTOS: Adds methods specific to CHIP_SYSTEM_CONFIG_USING_LWIP and CHIP_SYSTEM_CONFIG_USE_OPENTHREAD_ENDPOINT.
 *   - LayerSockets: Adds I/O event methods specific to CHIP_SYSTEM_CONFIG_USING_SOCKETS.
 *     - LayerSocketsLoop: Adds methods for event-loop-based implementations.
 *
 * Threading notes:
 *
 * The SDK is not generally thread safe. System::Layer methods should only be called from
 * a single context, or otherwise externally synchronized. For platforms that use a CHIP
 * event loop thread, timer callbacks are invoked on that thread; for platforms that use
 * a CHIP lock, the lock is held.
 */
class DLL_EXPORT Layer
{
public:
    Layer()          = default;
    virtual ~Layer() = default;

    /**
     * Initialize the Layer.
     */
    virtual CriticalFailure Init() = 0;

    /**
     * Shut down the Layer.
     *
     * Some other layers hold pointers to System::Layer, so care must be taken
     * to ensure that they are not used after calling Shutdown().
     */
    virtual void Shutdown() = 0;

    /**
     * True if this Layer is initialized. No method on Layer or its abstract descendants, other than this and `Init()`,
     * may be called from general code unless this is true. (Individual Impls may have looser constraints internally.)
     */
    virtual bool IsInitialized() const = 0;

    /**
     * @brief
     *   This method starts a one-shot timer.  This method must be called while in the Matter context (from
     *   the Matter event loop, or while holding the Matter stack lock).
     *
     *   @note
     *       Only a single timer is allowed to be started with the same @a aComplete and @a aAppState
     *       arguments. If called with @a aComplete and @a aAppState identical to an existing timer,
     *       the currently-running timer will first be cancelled.
     *
     *   @param[in]  aDelay             Time before this timer fires.
     *   @param[in]  aComplete          A pointer to the function called when timer expires.
     *   @param[in]  aAppState          A pointer to the application state object used when timer expires.
     *
     *   @return CHIP_NO_ERROR On success.
     *   @return CHIP_ERROR_NO_MEMORY If a timer cannot be allocated.
     *   @return Other Value indicating timer failed to start.
     */
    virtual CriticalFailure StartTimer(Clock::Timeout aDelay, TimerCompleteCallback aComplete, void * aAppState) = 0;

    /**
     * @brief
     *   This method extends the timer expiry to the provided aDelay. This method must be called while in the Matter context
     *   (from the Matter event loop, or while holding the Matter stack lock).
     *   aDelay is not added to the Remaining time of the timer. The finish line is pushed back to aDelay.
     *
     *   @note The goal of this method is that the timer remaining time cannot be shrunk and only extended to a new time
     *         If the provided new Delay is smaller than the timer's remaining time, the timer is left untouched.
     *         In the other case the method acts like StartTimer
     *
     *   @param[in]  aDelay             Time before this timer fires.
     *   @param[in]  aComplete          A pointer to the function called when timer expires.
     *   @param[in]  aAppState          A pointer to the application state object used when timer expires.
     *
     *   @return CHIP_NO_ERROR On success.
     *   @return CHIP_ERROR_INVALID_ARGUMENT If the provided aDelay value is 0
     *   @return CHIP_ERROR_NO_MEMORY If a timer cannot be allocated.
     *   @return Other Value indicating timer failed to start.
     */
    virtual CHIP_ERROR ExtendTimerTo(Clock::Timeout aDelay, TimerCompleteCallback aComplete, void * aAppState) = 0;

    /**
     * @brief
     *   This method searches for the timer matching the provided parameters.
     *   and returns whether it is still "running" and waiting to trigger or not.
     *
     *   @note This is used to verify by how long the ExtendTimer method extends the timer, as it may ignore an extension request
     *        if it is shorter than the current timer's remaining time.
     *
     *   @param[in]  onComplete         A pointer to the function called when timer expires.
     *   @param[in]  appState           A pointer to the application state object used when timer expires.
     *
     *   @return True if there is a current timer set to call, at some point in the future, the provided onComplete callback
     *           with the corresponding appState context. False otherwise.
     */
    virtual bool IsTimerActive(TimerCompleteCallback onComplete, void * appState) = 0;

    /**
     * @brief
     *   This method searches for the timer matching the provided parameters
     *   and returns the remaining time left before it expires.
     *   @param[in]  onComplete         A pointer to the function called when timer expires.
     *   @param[in]  appState           A pointer to the application state object used when timer expires.
     *
     *  @return The remaining time left before the timer expires.
     */
    virtual Clock::Timeout GetRemainingTime(TimerCompleteCallback onComplete, void * appState) = 0;

    /**
     * @brief This method cancels a one-shot timer, started earlier through @p StartTimer().  This method must
     *        be called while in the Matter context (from the Matter event loop, or while holding the Matter
     *        stack lock).
     *
     *   @note
     *       The cancellation could fail silently if the timer specified by the combination of the callback
     *       function and application state object couldn't be found.
     *
     *   @param[in]  aOnComplete   A pointer to the callback function used in calling @p StartTimer().
     *   @param[in]  aAppState     A pointer to the application state object used in calling @p StartTimer().
     *
     */
    virtual void CancelTimer(TimerCompleteCallback aOnComplete, void * aAppState) = 0;

    /**
     * @brief
     *   Schedules a `TimerCompleteCallback` to be run as soon as possible in the Matter context.
     *
     *  WARNING: This must only be called when already in the Matter context (from the Matter event loop, or
     *           while holding the Matter stack lock). The `PlatformMgr::ScheduleWork()` equivalent method
     *           is safe to call outside Matter context.
     *
     * @param[in] aComplete     A pointer to a callback function to be called when this timer fires.
     * @param[in] aAppState     A pointer to an application state object to be passed to the callback function as argument.
     *
     * @retval CHIP_ERROR_INCORRECT_STATE   If the System::Layer has not been initialized.
     * @retval CHIP_ERROR_NO_MEMORY         If the SystemLayer cannot allocate a new timer.
     * @retval CHIP_NO_ERROR                On success.
     */
    virtual CriticalFailure ScheduleWork(TimerCompleteCallback aComplete, void * aAppState) = 0;

    /**
     * @brief
     *   Schedules a lambda object to be run as soon as possible in the Matter context.
     *
     * This is safe to call from any context and will guarantee execution in Matter context.
     * Note that the Lambda's capture have to fit within `CHIP_CONFIG_LAMBDA_EVENT_SIZE` bytes.
     *
     * @param[in] lambda The Lambda to execute in Matter context.
     *
     * @retval CHIP_NO_ERROR On success.
     * @retval other Platform-specific errors generated indicating the reason for failure.
     */
    template <typename Lambda>
    CriticalFailure ScheduleLambda(const Lambda & lambda)
    {
        static_assert(std::is_invocable_v<Lambda>, "lambda argument must be an invocable with no arguments");
        LambdaBridge bridge;
        bridge.Initialize(lambda);
        return ScheduleLambdaBridge(std::move(bridge));
    }

private:
    CriticalFailure ScheduleLambdaBridge(LambdaBridge && bridge);

    // Not copyable
    Layer(const Layer &)             = delete;
    Layer & operator=(const Layer &) = delete;
};

#if CHIP_SYSTEM_CONFIG_USE_LWIP || CHIP_SYSTEM_CONFIG_USE_OPENTHREAD_ENDPOINT

class LayerFreeRTOS : public Layer
{
};

#endif // CHIP_SYSTEM_CONFIG_USE_LWIP

#if CHIP_SYSTEM_CONFIG_USE_SOCKETS
class LayerSockets : public Layer
{
public:
    /**
     * Initialize watching for events on a file descriptor.
     *
     * Returns an opaque token through @a tokenOut that must be passed to subsequent operations for this file descriptor.
     * Multiple calls to start watching the same file descriptor will return the same token.
     * StopWatchingSocket() must be called before closing the file descriptor.
     */
    virtual CHIP_ERROR StartWatchingSocket(int fd, SocketWatchToken * tokenOut) = 0;

    /**
     * Register a callback function.
     *
     * The callback will be invoked (with the CHIP stack lock held) when requested event(s) are ready.
     */
    virtual CHIP_ERROR SetCallback(SocketWatchToken token, SocketWatchCallback callback, intptr_t data) = 0;

    /**
     * Request a callback when the associated file descriptor is readable.
     */
    virtual CHIP_ERROR RequestCallbackOnPendingRead(SocketWatchToken token) = 0;

    /**
     * Request a callback when the associated file descriptor is writable.
     */
    virtual CHIP_ERROR RequestCallbackOnPendingWrite(SocketWatchToken token) = 0;

    /**
     * Cancel a request for a callback when the associated file descriptor is readable.
     */
    virtual CHIP_ERROR ClearCallbackOnPendingRead(SocketWatchToken token) = 0;

    /**
     * Cancel a request for a callback when the associated file descriptor is writable.
     */
    virtual CHIP_ERROR ClearCallbackOnPendingWrite(SocketWatchToken token) = 0;

    /**
     * Stop watching for events on the associated file descriptor.
     *
     * This MUST be called before the file descriptor is closed.
     * It is not necessary to clear callback requests before calling this function.
     */
    virtual CHIP_ERROR StopWatchingSocket(SocketWatchToken * tokenInOut) = 0;

    /**
     * Return a SocketWatchToken that is guaranteed not to be valid. Clients may use this to initialize variables.
     */
    virtual SocketWatchToken InvalidSocketWatchToken() = 0;
};

class LayerSocketsLoop;

/**
 * EventLoopHandlers can be registered with a LayerSocketsLoop instance to enable
 * participation of those handlers in the processing cycle of the event loop. This makes
 * it possible to implement adapters that allow components utilizing a third-party event
 * loop API to participate in the Matter event loop, instead of having to run an entirely
 * separate event loop on another thread.
 *
 * Specifically, the `PrepareEvents` and `HandleEvents` methods of registered event loop
 * handlers will be called from the LayerSocketsLoop methods of the same names.
 *
 * @see LayerSocketsLoop::PrepareEvents
 * @see LayerSocketsLoop::HandleEvents
 */
class EventLoopHandler : public chip::IntrusiveListNodeBase<>
{
public:
    virtual ~EventLoopHandler() {}

    /**
     * Prepares events and returns the next requested wake time.
     */
    virtual Clock::Timestamp PrepareEvents(Clock::Timestamp now) { return Clock::Timestamp::max(); }

    /**
     * Handles / dispatches pending events.
     * Every call to this method will have been preceded by a call to `PrepareEvents`.
     */
    virtual void HandleEvents() = 0;

private:
    // mState is provided exclusively for use by the LayerSocketsLoop implementation
    // sub-class and can be accessed by it via the LayerSocketsLoop::LoopHandlerState() helper.
    friend class LayerSocketsLoop;
    intptr_t mState = 0;
};

class LayerSocketsLoop : public LayerSockets
{
public:
    virtual void Signal()          = 0;
    virtual void EventLoopBegins() = 0;
    virtual void PrepareEvents()   = 0;
    virtual void WaitForEvents()   = 0;
    virtual void HandleEvents()    = 0;
    virtual void EventLoopEnds()   = 0;

    virtual void AddLoopHandler(EventLoopHandler & handler)    = 0;
    virtual void RemoveLoopHandler(EventLoopHandler & handler) = 0;

#if CHIP_SYSTEM_CONFIG_USE_LIBEV
    virtual void SetLibEvLoop(struct ev_loop * aLibEvLoopP) = 0;
    virtual struct ev_loop * GetLibEvLoop()                 = 0;
#endif // CHIP_SYSTEM_CONFIG_USE_LIBEV

protected:
    // Expose EventLoopHandler.mState as a non-const reference to sub-classes
    decltype(EventLoopHandler::mState) & LoopHandlerState(EventLoopHandler & handler) { return handler.mState; }
};

#endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS

#if CHIP_SYSTEM_CONFIG_USE_DISPATCH
class LayerDispatch :
#if CHIP_SYSTEM_CONFIG_USE_SOCKETS
    public LayerSockets
#else
    public Layer
#endif
{
public:
    virtual void SetDispatchQueue(dispatch_queue_t dispatchQueue)  = 0;
    virtual dispatch_queue_t GetDispatchQueue()                    = 0;
    virtual void HandleDispatchQueueEvents(Clock::Timeout timeout) = 0;

    /**
     * Schedule a block to run asynchronously.
     *
     * @param block The block to be executed.
     *
     * @note This method is thread-safe and can be called from any dispatch queue.
     */
    virtual CriticalFailure ScheduleWorkWithBlock(dispatch_block_t block)                     = 0;
    virtual CriticalFailure StartTimerWithBlock(dispatch_block_t block, Clock::Timeout delay) = 0;
};
#endif

} // namespace System
} // namespace chip
